This file will look at changing the default theme of ggplot(), adding custom annotations, and combining multiple plots into a single figure. Also, some very basics of making tables!

# libraries
library(tidyverse)
library(tigris)
library(ggthemes)
library(patchwork)

library(knitr)
library(kableExtra)
library(DT)

# data
gag <- readRDS("data/gaglaws.RDS")
gag_long <- readRDS("data/gaglaws_long.RDS")
states48 <- readRDS("data/state_shapefile.RDS")

The example data was collected by PEN America to understand the ongoing state legislative efforts to censor America’s classrooms.

Changing themes

Let’s start with a basic figure, a barplot of the number of educational gag bills introduced in the past year within each state.

# create state-level gag order df
gag_state <- gag %>% 
  group_by(State) %>% 
  summarize(numbills = sum(target_k12)) 

# Initial figure
ggplot(gag_state, aes(y = fct_reorder(State, numbills), x = numbills)) +
  geom_col() +
  labs(x = "", y = "", title = "Gag Laws Targeting K-12 Education") 

There are two primary ways to change the overall look or theme of the plot:

  1. Use a pre-built theme (from ggplot or another package like ggthemes)
  2. Invoke the theme() function and change individual elements

The following version does both – using theme_few() from ggthemes and making the y-axis text a little smaller.

# Changing theme (and adding color)
ggplot(gag_state, aes(y = fct_reorder(State, numbills), x = numbills)) +
  geom_col(fill = "slategray3") +
  labs(x = "", y = "", title = "Gag Laws Targeting K-12 Education") +
  theme_few() + # theme_minimal 
  theme(axis.text.y = element_text(size = 8))

Adding annotations

Schwabish and others also suggested using additional text on the figures to provide context or otherwise support interpretation. We can do that with the annotate() function!

# n_distinct(gag$State) # value for annotation

ggplot(gag_state, aes(y = fct_reorder(State, numbills), x = numbills)) +
  geom_col(fill = "slategray4") +
  labs(x = "", y = "", title = "") +
  annotate("text", x = 15, y = 15, color = "slategray", size = 10,
           label = "42 states had at least one bill\n introduced to limit teaching\n in K-12 schools in 2021-2022") +
  theme_few() +  
  theme(axis.text.y = element_text(size = 10))

Combining plots

Schwabish, in particular, also advised that a map – while pretty cool looking – may not be sufficient on it’s own if we want readers to better understand the values being presented on a choropleth map. So let’s make a map and them combine it with the bar plot above.

# join gag_state to states
gag_state_sf <- left_join(states48, gag_state,
                          by = c("NAME" = "State"))  

gag_state_sf <- gag_state_sf %>% 
  mutate(numbills = replace_na(numbills, 0))

gag_state_sf <- gag_state_sf %>% 
  mutate(binbills = cut(numbills, right = FALSE,
                        breaks = c(0, 1, 3, 6, 10, 15, 25),
                        labels = c("0", "1-2", "3-5", "6-9",
                                   "10-14", "15-25"),
                        ordered_result = TRUE)) 

ggplot(gag_state_sf) +
  geom_sf(aes(fill = binbills)) +
  scale_fill_viridis_d(option = "plasma", name = "# Bills") +
  theme_void()

To put them together, we can use patchwork!

# to patch, we need to save the figures we want to combine as objects

bar <- gag_state_sf %>% filter(numbills > 0) %>% 
  ggplot(aes(x = fct_rev(fct_reorder(NAME, numbills)), y = numbills)) +
  geom_col(aes(fill = binbills)) +
  scale_fill_viridis_d(option = "plasma", guide = "none") +
  labs(x = "", y = "", title = "") +
  annotate("text", x = 20, y = 5, color = "slategray", hjust = 0, size = 8,
           label = "42 states had at least one bill\n introduced to limit teaching\n in K-12 schools in 2021-2022") +
  theme_solid() +  
  theme(axis.text.x = element_text(size = 12, angle = 45, hjust = 1, vjust = 1.5))

map <- ggplot(gag_state_sf) +
  geom_sf(aes(fill = binbills)) +
  scale_fill_viridis_d(option = "plasma", name = "# Bills") +
  theme_void() +
  theme(legend.position = "bottom")

# bar + map +
#   plot_annotation(title = "Number of Gag Bills Targeting K-12 Education",
#                   caption = "Data collected by PEN America: https://pen.org/report/americas-censored-classrooms/")

# map / bar +
  # plot_annotation(title = "Number of Gag Bills Targeting K-12 Education",
  #                 caption = "Data collected by PEN America: https://pen.org/report/americas-censored-classrooms/")

bar + inset_element(map, .1, .3, 1, 1) +
   plot_annotation(title = "Number of Gag Bills Targeting K-12 Education",
                  caption = "Data collected by PEN America: https://pen.org/report/americas-censored-classrooms/")

Tables!

Sometimes we just want to present data in a table – if it’s a small amount of data (limited rows, limited columns), the kable and kableExtra packages are useful and easy

gag_state %>% 
  kbl(align = "c") %>% 
  kable_styling(bootstrap_options = c("striped", "hover")) %>% 
  scroll_box(height = "300px")
State numbills
Alabama 5
Alaska 4
Arizona 7
Arkansas 5
Colorado 1
Connecticut 1
Florida 3
Georgia 5
Idaho 4
Illinois 2
Indiana 8
Iowa 6
Kansas 4
Kentucky 6
Louisana 1
Louisiana 3
Maryland 1
Michigan 2
Minnesota 2
Mississippi 10
Missouri 24
Nebraska 1
New Hampshire 2
New Jersey 3
New Mexico 1
New York 2
North Carolina 4
North Dakota 1
Ohio 3
Oklahoma 11
Pennsylvania 3
Rhode Island 1
South Carolina 9
South Dakota 4
Tennessee 4
Texas 5
Utah 1
Virginia 7
Washington 2
West Virginia 7
Wisconsin 1
Wyoming 2

If we’re trying to show a lot of data, or make it easy for readers to view (part of) the raw data themselves, the datatable package is a good place to start.

# Make columns individually filterable, change pagelength
gag %>% 
  select(c(1:5, 7,8)) %>% 
  datatable(filter = 'top', options = list(pageLength = 5),
    colnames = c('State', 'Bill Number', 'Date', 'Status', 'Sponsor', 'Targets', 'Enforcement'),
    caption = 'Data collected by PEN America: https://pen.org/report/americas-censored-classrooms/')